//////////////////////////////////////////////////////////////////
// MattiseOS Kernel
// Date: 2008
// Author: Matthew Iselin
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//////////////////////////////////////////////////////////////////

// Includes
#include <mattise.h>
#include <stdint.h>
#include <mem/util.h>
#include <kernio/io.h>
#include <core/portio.h>
#include <process/vmm.h>
#include <process/common.h>
#include <core/intrcommon.h>

// Functions

// the virtual mode monitor
void kVmm( struct stack_layout* stack )
{
    dprintf( "in kVmm now\n" );

    // grab the instruction pointer
    uint8_t* ip = (uint8_t*) FAR_POINTER_TO_LINEAR( stack->cs, stack->eip );

    // setup the IVT (always at offset 0)
    uint16_t* ivt = (uint16_t*) 0;

    // grab the stack
    uint16_t* vmstack = (uint16_t*) FAR_POINTER_TO_LINEAR( stack->ss, stack->useresp );
    uint32_t* vmstack32 = (uint32_t*) vmstack;

    // 32-bit things can be used by 16-bit programs (bends logic a bit), so we figure out
    // how to handle such weirdness using these
    uint32_t op32 = 0, addr32 = 0;

    // what instruction was attempted?
    while( 1 )
    {
        dprintf( "op: 0x%x\n", ip[0] );
        switch( ip[0] )
        {
            // 32-bit operand
            case 0x66:

                // we're working in 32-bit operands now
                op32 = 1;

                // increment the instruction pointer
                ip++;
                stack->eip++;

                break;

            // 32-bit address
            case 0x67:

                // working with 32-bit addresses now
                addr32 = 1;

                // increment the instruction pointer
                ip++;
                stack->eip++;

                break;

            // INT n
            case 0xCD:

                // interupt 80 with ax=0xdead ends the task
                // TODO: integrate with real syscalls
                if( ip[1] == 0x80 && ((stack->eax & 0xFFFF) == 0xdead) )
                {
                    // actually loop forever for now
                    while( 1 );

                    // kill the task
                    currprocess->status = PSTATE_INVALID;

                    // reschedule
                    kSchedule(0);

                    // return so that the new task is loaded
                    return;
                }

                // push three values onto the stack
                vmstack -= 3;
                vmstack[2] = (uint16_t) (stack->eflags);
                vmstack[1] = (uint16_t) (stack->cs);
                vmstack[0] = (uint16_t) (stack->eip + 2); // skip past the 2 byte "n"

                // update the stack
                stack->useresp -= 6;
                stack->useresp &= 0xFFFF;

                // setup the CS:EIP pair to return to
                stack->cs = ivt[ip[1] * 2 + 1]; // segment - each entry is two bytes, and they go EIP:CS
                stack->eip = ivt[ip[1] * 2]; // instruction pointer

                // return to the caller
                return;

            // pushf (basically pushes the flags)
            case 0x9c:

                // 32-bit operands?
                if( op32 )
                {
                    // take 4 bytes off the stack
                    stack->useresp -= 4;
                    stack->useresp &= 0xFFFF;
                    *vmstack32-- = stack->eflags;
                }
                else
                {
                    // take 2 bytes off the stack
                    stack->useresp -= 2;
                    stack->useresp &= 0xFFFF;
                    *vmstack-- = (uint16_t) stack->eflags;
                }

                // set the new instruction pointer
                stack->eip++;
                return;

            // popf (pops the flags)
            case 0x9d:

                // 32-bit operands?
                if( op32 )
                {
                    // pop the data off the stack
                    stack->eflags = (*vmstack32++) | 0x20000;

                    // set the correct esp
                    stack->useresp += 4;
                    stack->useresp &= 0xFFFF;
                }
                else
                {
                    // pop the data off the stack
                    stack->eflags = *vmstack++;

                    // set the correct esp
                    stack->useresp += 2;
                    stack->useresp &= 0xFFFF;
                }

                // set the new instruction pointer
                stack->eip++;
                return;

            // iret
            case 0xcf:

                // set the correct stack up
                stack->eip = vmstack[0];
                stack->cs = vmstack[1];
                stack->eflags = vmstack[2] | 0x20000;

                // set the correct esp
                stack->useresp += 6;
                stack->useresp &= 0xFFFF;

                // done!
                return;
        }
    }

    // loop forever - invalid opcode
    while( 1 );
}
